﻿using System;
using System.IO;
using System.Collections.Generic;
//using Microsoft.Xna.Framework.Content;
using FarseerPhysics.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using SpriteGame.Rendering;
using SpriteGame.UI;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Collision;
using System.Xml.Serialization;
using System.Threading;

#if XBOX
using Microsoft.Xna.Framework.Content;
#else
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Intermediate;
#endif

using Path = System.IO.Path;
using System.Xml;
using FarseerPhysics;
using SpriteGame.Helpers;
using FarseerPhysics.Dynamics.Contacts;
using System.Diagnostics;
using SpriteGame.Effects.Blood;
using SpriteGame.Effects.Particles;


namespace SpriteGame
{
    public abstract class Scene:IFocusable
    {
        [XmlIgnore]
        protected Game1 parent;
        [XmlIgnore]
        public Game1 Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        UIPanel hud;
        internal UIPanel Hud
        {
            get { return hud; }
        }

        //Content Stuff
        [XmlIgnore]
        protected ContentManager content;

        bool isActive = false;
        public bool IsActive
        {
            get { return isActive; }
            set { isActive = value; }
        }

        //Physics Stuff
        [XmlIgnore]
        private World world;

        [XmlIgnore]
        /// <summary>
        /// Gets the currently active physics world
        /// </summary>
        public World World
        {
            get {
                //lock (world)
                //{
                    return world;
               // }
            }
        }
        [XmlIgnore]
        protected int stepCount = 0;

        //Graphics Stuff
        [XmlIgnore]
        ShapeRenderer spriteBatch;

        bool physicsPaused = false;
        public bool PausedPhysics
        {
            get { return physicsPaused; }
            set { physicsPaused = value; }
        }


        protected List<BaseSprite> entities;
        [XmlIgnore]
        List<BaseSprite> entitiesToRemove;
        [XmlIgnore]
        List<BaseSprite> entitiesToAdd;

        Particles particles;
        public Particles Particles
        {
            get { return particles; }
            set { particles = value; }
        }

        public Scene()
        {

        }
        
        public Scene(Game1 _parent)
        {
            parent = _parent;
        }

        public Scene(Game1 _parent, string _levelName)
        {
            parent = _parent;
            LoadScene(_levelName);
        }

        static readonly object physicsLock = new object();
        Thread physicsThread;

        const int numMiliseconds = 333;
        static long elapsedPhysicsTime = 0;

        void updatePhysics(object state)
        {
            elapsedPhysicsTime += numMiliseconds;

            float timeStep = Math.Min((float)elapsedPhysicsTime * 0.001f, (1f / 30f));
            world.Step(timeStep);
            if (timeStep > 0.0f)
            {
                ++stepCount;
            }
        }

        Stopwatch physicsTimer;

        void runPhysicsThread()
        {
            //while (true)
            //{
            //    float timeStep = physicsTimer.ElapsedMilliseconds;

            //    if (timeStep > 0 || true)
            //    {
            //        lock (physicsLock)
            //        {
            //            World.Step(1/30);
            //        }
            //    }

            //    Thread.Sleep(1 / 25);
            //    physicsTimer.Restart();
            //}
            ////Clean up the thread
            //physicsThread.Abort();
            //physicsThread = null;
            double lastMilliseconds = 0;
            while (true)
            {
                double physStart = (physicsTimer.Elapsed.TotalMilliseconds - lastMilliseconds);
                lock(physicsLock)
                {
                    float timeStep = Math.Min((float)(physicsTimer.Elapsed.TotalMilliseconds - lastMilliseconds) * 0.001f, (1f / 60f));
                    //world.Step((float)(physicsTimer.Elapsed.TotalMilliseconds - lastMilliseconds) * 0.1f);
                    world.Step((1f/60f));
                    //Thread.Sleep(TimeSpan.FromSeconds(0.0015));
                    //physicsTimer.Restart();
                    lastMilliseconds = physicsTimer.Elapsed.TotalMilliseconds;
                }
                double physEnd = (physicsTimer.Elapsed.TotalMilliseconds - lastMilliseconds);
                Thread.Sleep(TimeSpan.FromMilliseconds(Math.Max(0,0.0015 - (physEnd - physStart))));
            }
        }

        public void LockPhysics()
        {
            //System.Threading.Monitor.Enter(physicsLock);
        }

        public void UnlockPhysics()
        {
            //System.Threading.Monitor.Exit(physicsLock);
        }

        Effect blurShader;
        /// <summary>
        /// Loads the content to be used in the scene
        /// </summary>
        public virtual void LoadContent()
        {
            content = parent.Content;
            world = new World(new Vector2(0.0f, -50f));
            blurShader = content.Load<Effect>("Effects/gaussianblur");
            BloodParticleManager.Instance.InitBloodParticleManager(5000, parent.GraphicsDevice.PresentationParameters.BackBufferWidth, parent.GraphicsDevice.PresentationParameters.BackBufferHeight, parent.GraphicsDevice, content);
            ParticleManager.Instance.InitParticleManager(50000, parent.GraphicsDevice);
            
            compositeSpriteBatch = new SpriteBatch(parent.GraphicsDevice);

            //world.ContactManager.PreSolve += PreSolve;
            //world.ContactManager.PostSolve += PostSolve;
            //world.ContactManager.BeginContact += BeginContact;
           // world.ContactManager.EndContact += EndContact;

            ScreenResized(parent.GraphicsDevice.Viewport.Width, parent.GraphicsDevice.Viewport.Height);

            //TimerCallback callback = new TimerCallback(updatePhysics);
           // Timer t = new Timer(callback, this, 0, numMiliseconds);



            entities = new List<BaseSprite>();
            entitiesToRemove = new List<BaseSprite>();
            entitiesToAdd = new List<BaseSprite>();

            spriteBatch = new ShapeRenderer(parent.GraphicsDevice, parent);
            spriteBatch.LoadContent();

            BuildHUD();

            particles = new Particles(this);

            //physicsTimer = new Stopwatch();
           // physicsTimer.Start();
            //physicsThread = new Thread(new ThreadStart(runPhysicsThread));
            //physicsThread.Start();

#if !WINDOWS
            GC.Collect();
            //Thread.Sleep(TimeSpan.FromSeconds(5));
#endif
        }

        public virtual void UnloadContent()
        {
            foreach (BaseSprite s in entities)
            {
                RemoveEntity(s);
            }
            //ensures that entities are not removed from the entlist until all updates are complete
            foreach (BaseSprite s in entitiesToRemove)
            {
                s.Dispose();
                entities.Remove(s);
            }
            entitiesToRemove.Clear();
            entities.Clear();
            entitiesToAdd.Clear();
        }

        /// <summary>
        /// Used to create the current UI instance
        /// </summary>
        public virtual void BuildHUD()
        {
            hud = new UIContainerPanel(0, 0, parent.GraphicsDevice.Viewport.Width, parent.GraphicsDevice.Viewport.Height);
        }

        /// <summary>
        /// Adds an entity to the current entity tree
        /// </summary>
        /// <param name="_e"></param>
        public void AddEntity(BaseSprite _e)
        {
            entitiesToAdd.Add(_e);
        }

        /// <summary>
        /// Adds entities to the current entity tree
        /// </summary>
        /// <param name="_e"></param>
        public void AddEntity(BaseSprite[] _e)
        {
            foreach (BaseSprite s in _e)
            {
                entitiesToAdd.Add(s);
            }
        }

        /// <summary>
        /// Removes the last entity to be added to the tree
        /// </summary>
        public void RemoveLastEntity()
        {
            if (entities.Count > 0)
            {
                entitiesToRemove.Add(entities[entities.Count - 1]);
            }
        }


        /// <summary>
        /// Removes an entity from the current tree DO NOT MODIFY LOUIS HACKS ONLY!
        /// </summary>
        /// <param name="_e"></param>
        public virtual void RemoveEntity(BaseSprite _e)
        {
            entitiesToRemove.Add(_e);
        }

        public void LoadScene(string _sceneName)
        {
            SceneDef sceneDef = content.Load<SceneDef>("Levels/" + _sceneName);

            if (sceneDef.GeometryPool != null)
            {
                GeometryPool.DumpLibrary(sceneDef.GeometryPool);
            }

            if (entities.Count > 0)
            {
                foreach (BaseSprite e in entities)
                {
                    RemoveEntity(e);
                }
            }

            foreach (ObjectDef d in sceneDef.Objects)
            {
                Type t = Type.GetType(d.type);

                BaseSprite sprite = (BaseSprite)Activator.CreateInstance(t);
                sprite.StartPosition = d.position;
                sprite.StartRot = d.rotation;
                sprite.Texture = d.texture;
                sprite.BodyType = d.bodyType;
                AddEntity(sprite);
            }

            GC.Collect();
        }

        [XmlInclude(typeof(BodyType))]
        [XmlInclude(typeof(BaseSprite))]
        [XmlInclude(typeof(Vertices))]
        public void SaveScene(string _sceneName)
        {
            SceneDef sceneDef = new SceneDef();
            sceneDef.Objects = new List<ObjectDef>();
            sceneDef.GeometryPool = GeometryPool.GetLibrary();

            //List<ObjectDef> objects = new List<ObjectDef>();

            foreach (BaseSprite e in entities)
            {
                if (e.CanSave)
                {
                    ObjectDef def = new ObjectDef();
                    def.type = e.GetType().ToString();
                    def.position = e.Position;
                    def.rotation = e.Rotation;
                    def.bodyType = e.BodyType;
                    def.texture = e.Texture;

                    sceneDef.Objects.Add(def);
                }
            }

            if (sceneDef.Objects.Count > 0)
            {
#if WINDOWS 
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.Indent = true;

                using (XmlWriter writer = XmlWriter.Create("../../../Content/Levels/"+_sceneName+".xml", settings))
                {
                    IntermediateSerializer.Serialize(writer, sceneDef, null);
                } 
#endif
            }

        }

        public virtual void Update(GameTime _time)
        {
            foreach (BaseSprite s in entitiesToAdd)
            {
                entities.Add(s);
                if (!s.IsLoaded)
                {
                    s.LoadContent(this);
                }
            }

            entitiesToAdd.Clear();

            //ensures that entities are not removed from the entlist until all updates are complete
            foreach (BaseSprite s in entitiesToRemove)
            {
                s.Dispose();
                entities.Remove(s);
                
            }
            entitiesToRemove.Clear();



            if (!physicsPaused)
            {
                
                float timeStep = Math.Min((float)_time.ElapsedGameTime.TotalMilliseconds * 0.001f, (1f / 60f));
                world.Step(timeStep);
                if (timeStep > 0.0f)
                {
                    ++stepCount;
                }
            }

            for (int i = 0; i < entities.Count; i++)
            {
                entities[i].Update(_time);
            }

            if (InputHandler.LeftClick())
            {
                if (hud.Bounds.Contains(InputHandler.GetMousePoint()))
                    hud.GetCollider(InputHandler.GetMousePoint());
            }

            if (InputHandler.IsGamepadButtonPressed(Buttons.Start))
            {
                parent.ChangeScene(SceneIndex.MENU, "");
            }

            particles.Update(_time);

            hud.Update(Vector2.Zero);
        }

        public virtual void DrawHUD(SpriteBatch _batch)
        {
            if (hud.IsVisible)
            {
                hud.Render(_batch, Vector2.Zero);
            }
        }

        List<BaseSprite> spritesToDraw = new List<BaseSprite>();
        RenderTarget2D backgroundComposite;
        RenderTarget2D glowComposite;
        RenderTarget2D sceneGeometry;
        RenderTarget2D physicsGeometry;
        RenderTarget2D bloodGeometry;
        RenderTarget2D particleGeometry;
        RenderTarget2D compositeLayer;

        SpriteBatch compositeSpriteBatch;

        int screenWidth;
        int screenHeight;

        public void ScreenResized(int _w, int _h)
        {
            screenWidth = _w;
            screenHeight = _h;

            backgroundComposite = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            glowComposite = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            
            sceneGeometry = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            physicsGeometry = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            bloodGeometry = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            particleGeometry = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);
            compositeLayer = new RenderTarget2D(parent.GraphicsDevice, _w, _h, false, SurfaceFormat.Color, DepthFormat.None);

        }

        public virtual void Draw(GameTime _time)
        {
            // Make a small box.
            AABB aabb;
            Vector2 d = new Vector2(0.1f, 0.1f);
            Vector2 screenBounds = (new Vector2(1280, 720) * 0.07f) * 1.0f;
            aabb.LowerBound = -screenBounds + Parent.ViewCenter;
            aabb.UpperBound = screenBounds + Parent.ViewCenter;
            //aabb.LowerBound = parent.ConvertScreenToWorld(0,0) - d;
            //aabb.UpperBound = parent.ConvertScreenToWorld(parent.GraphicsDevice.Viewport.Width,
            //parent.GraphicsDevice.Viewport.Height) + d;
            if (FarseerPhysics.DebugViewXNA.DebugViewXNA.Instance != null)
                FarseerPhysics.DebugViewXNA.DebugViewXNA.Instance.DrawAABB(ref aabb, Color.Red);
            //FarseerPhysics.DebugViewXNA.DebugViewXNA.Instance.DrawAABB(ref aabb, Color.Red);

            // Query the world for overlapping shapes.

            spritesToDraw.Clear();

            World.QueryAABB(
                fixture =>
                {
                    Body body = fixture.Fixture.Body;

                    spritesToDraw.Add((BaseSprite)body.UserData);
                    return true;
                }, ref aabb);
            parent.GraphicsDevice.BlendState = BlendState.AlphaBlend;
            //.AlphaBlendEnable = true;
            //parent.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            //parent.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            Rectangle screenBoundingRect = new Rectangle(0, 0, screenWidth, screenHeight);
            GraphicsDevice device = Parent.GraphicsDevice;
            BaseSprite sprite;

            device.SetRenderTarget(sceneGeometry);
            device.Clear(Color.Transparent);

            spriteBatch.BeginBatch();
            for (int i = 0; i < spritesToDraw.Count; i++)
            {
                sprite = spritesToDraw[i];

                if (sprite.BodyType == BodyType.Static)
                {
                    spritesToDraw[i].Draw(spriteBatch);
                }
            }
            spriteBatch.EndBatch();

            device.SetRenderTarget(physicsGeometry);
            device.Clear(Color.Transparent);

            spriteBatch.BeginBatch();
            for (int i = 0; i < spritesToDraw.Count; i++)
            {
                sprite = spritesToDraw[i];

                if (sprite.BodyType != BodyType.Static)
                {
                    spritesToDraw[i].Draw(spriteBatch);
                }
            }
            spriteBatch.EndBatch();



            device.SetRenderTarget(bloodGeometry);
            //device.Clear(Color.Transparent);
            Color bloodColor = new Color(0.3f, 0, 0);
            BloodParticleManager.Instance.DrawBlood(spriteBatch, bloodColor, bloodGeometry);


            device.SetRenderTarget(glowComposite);
            device.Clear(Color.Transparent);

            spriteBatch.BeginBatch();
            for (int i = 0; i < spritesToDraw.Count; i++)
            {
                sprite = spritesToDraw[i];

                if (sprite.Glow)
                {
                    spritesToDraw[i].Draw(spriteBatch);
                }
            }
            spriteBatch.EndBatch();

            device.SetRenderTarget(particleGeometry);
            device.Clear(Color.Transparent);
            ParticleManager.Instance.Draw(spriteBatch);


            device.SetRenderTarget(null);

            device.Clear(Color.Black);

            compositeSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.PointClamp, null, null);
            blurShader.CurrentTechnique = blurShader.Techniques["verti"];
            compositeSpriteBatch.Draw(sceneGeometry, screenBoundingRect, Color.White);
            compositeSpriteBatch.Draw(physicsGeometry, screenBoundingRect, Color.White);
            compositeSpriteBatch.Draw(bloodGeometry, screenBoundingRect, Color.White);
            compositeSpriteBatch.Draw(particleGeometry, screenBoundingRect, Color.White);

            compositeSpriteBatch.End();

            compositeSpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Additive, SamplerState.PointClamp, null, null, blurShader);
            blurShader.CurrentTechnique = blurShader.Techniques[0];
            compositeSpriteBatch.Draw(glowComposite, screenBoundingRect, Color.White);
            blurShader.CurrentTechnique = blurShader.Techniques[1];
            compositeSpriteBatch.Draw(glowComposite, screenBoundingRect, Color.White);
            compositeSpriteBatch.End();

            //for (int i = 0; i < spritesToDraw.Count; i++)
            //{
            //    spritesToDraw[i].Draw(spriteBatch);
            //}


            //foreach (BaseSprite s in entities)
            //{
            //    s.Draw(spriteBatch);
            //}

            spritesToDraw.Clear();

            //particles.Draw(spriteBatch);

        }

        #region IFocusable Members

        public bool HasFocus
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public void UpdateInput(GameTime _time)
        {
            throw new NotImplementedException();
        }

        protected virtual void BeginContact(Contact contact)
        {
        }

        protected virtual void EndContact(Contact contact)
        {
        }

        protected virtual void PreSolve(Contact contact, ref Manifold oldManifold)
        {
        }

        protected virtual void PostSolve(Contact contact, ref ContactImpulse impulse)
        {
        }

        #endregion
    }
}
